Imports BitSmoke.Core.Data
Imports BitSmoke.Core.Net.Connection

Imports System.Net
Imports System.Net.Sockets
Imports System.Net.NetworkInformation

Namespace Core.Net.Presence
    Public Class Presence
        Implements IDisposable

#Region " Public Constants "
        Public Const ProtocolVersion As Byte = 0
#End Region

#Region " Private Properties "
        Private zI_AnnouncePort As UInt16
        Private zI_ConnectionPort As UInt16

        Private zGUID As Guid
        Private zBA_GUID As Byte()

        Private zUDP As New UdpClient(AddressFamily.InterNetwork)

        Private zTCPLA As ArrayList

        Private zIPA_BroadcastInterfaces As ArrayList
        Private zIPA_Interfaces As ArrayList

        Private WithEvents zTmr As New Timers.Timer()
#End Region

#Region " Public Properties "
        Public Property connectionPort() As UInt16
            Get
                Return zI_ConnectionPort
            End Get

            Set(ByVal value As UInt16)
                zI_ConnectionPort = value

                listenOnAllInterfaces()
            End Set
        End Property

        Public Property announceFreq() As Double
            Get
                Return zTmr.Interval
            End Get

            Set(ByVal value As Double)
                zTmr.Interval = value
            End Set
        End Property
#End Region

#Region " Constructors "
        Public Sub New(ByVal pD_AnnounceFreq As Double, ByVal pI_AnnouncePort As UInt16, ByVal pGUID As Guid, ByVal pI_ConnectionPort As UInt16)
            zI_AnnouncePort = pI_AnnouncePort
            zI_ConnectionPort = pI_ConnectionPort
            zGUID = pGUID
            zBA_GUID = zGUID.ToByteArray

            updateInterfaces()

            listenOnAllInterfaces()

            ' set notifications for interfaces changing to rebuild the ip array
            AddHandler NetworkChange.NetworkAddressChanged, AddressOf AddressChangedCallback
            AddHandler NetworkChange.NetworkAvailabilityChanged, AddressOf AvailabilityChangedCallback

            zUDP.EnableBroadcast = True
            zUDP.Client.Bind(New IPEndPoint(IPAddress.Any, zI_AnnouncePort))
            zUDP.BeginReceive(New AsyncCallback(AddressOf zUDP_ReceiveCallback), zUDP)

            zTmr.Interval = pD_AnnounceFreq
            zTmr.Start()
        End Sub
#End Region

#Region " Private Methods "
        Private Sub updateInterfaces()
            Dim nics As NetworkInterface() = NetworkInterface.GetAllNetworkInterfaces()
            Dim adapter As NetworkInterface

            Dim ipaBroadcast As New ArrayList
            Dim ipa As New ArrayList
            Dim ip As IPAddress, mask As IPAddress, broadcast As IPAddress
            Dim uip As UnicastIPAddressInformation

            Dim iBroadcast As UInt32 = BitConverter.ToUInt32(IPAddress.Broadcast.GetAddressBytes, 0)
            Dim iIP As UInt32, iMask As UInt32, iResult As UInt32

            For Each adapter In nics
                If adapter.Supports(NetworkInformation.NetworkInterfaceComponent.IPv4) And adapter.OperationalStatus = OperationalStatus.Up Then
                    For Each uip In adapter.GetIPProperties.UnicastAddresses
                        ip = uip.Address
                        mask = uip.IPv4Mask

                        If Not IPAddress.IsLoopback(ip) Then
                            iIP = BitConverter.ToUInt32(ip.GetAddressBytes, 0)
                            iMask = BitConverter.ToUInt32(mask.GetAddressBytes, 0)
                            iResult = (iBroadcast Xor iMask) Or (iIP And iMask)

                            broadcast = New IPAddress(iResult)

                            ipaBroadcast.Add(broadcast)
                            ipa.Add(ip)
                        End If
                    Next
                End If
            Next

            zIPA_BroadcastInterfaces = ipaBroadcast
            zIPA_Interfaces = ipa
        End Sub

        Private Sub listenOnAllInterfaces()
            Dim tcpl As TcpListener

            For Each tcpl In zTCPLA
                tcpl.Stop()
                zTCPLA.Remove(tcpl)
                'TODO: should we be deleting any previous AsyncCallbacks? would need to keep track of them...
            Next

            Dim ipa As IPAddress
            For Each ipa In zIPA_Interfaces
                tcpl = New TcpListener(ipa, zI_ConnectionPort)

                zTCPLA.Add(tcpl)

                tcpl.Start()
                tcpl.BeginAcceptTcpClient(New AsyncCallback(AddressOf zTCPLA_AcceptCallback), tcpl)
            Next
        End Sub
#End Region

#Region " Callbacks "
        Private Sub AddressChangedCallback(ByVal sender As Object, ByVal e As EventArgs)
            updateInterfaces()
        End Sub

        Private Sub AvailabilityChangedCallback(ByVal sender As Object, ByVal e As NetworkAvailabilityEventArgs)
            updateInterfaces()
        End Sub

        Private Sub zTmr_Elapsed(ByVal sender As Object, ByVal e As System.Timers.ElapsedEventArgs) Handles zTmr.Elapsed
            'rebuild the announce packet every time, just in case the user changed the connection port
            Dim baBuffer As Byte() = New Byte(19) {}
            baBuffer(0) = ProtocolVersion 'tells the reciever how to parse the rest of the presence packet
            baBuffer(1) = DataConnection.ProtocolVersion 'the data connection protocol could have changed even though the presence protocol hasn't
            zBA_GUID.CopyTo(baBuffer, 2)
            BitConverter.GetBytes(zI_ConnectionPort).CopyTo(baBuffer, 18)

            Dim ipa As IPAddress
            For Each ipa In zIPA_BroadcastInterfaces
                zUDP.Send(baBuffer, baBuffer.Length, New IPEndPoint(ipa, zI_AnnouncePort))
            Next
        End Sub

        Private Sub zUDP_ReceiveCallback(ByVal ar As IAsyncResult)
            Dim udp As UdpClient = CType(ar.AsyncState, UdpClient)

            udp.BeginReceive(New AsyncCallback(AddressOf zUDP_ReceiveCallback), udp) ' can we really do this before the endreceive?

            Dim ipep As IPEndPoint = Nothing
            Dim baBuffer As Byte() = udp.EndReceive(ar, ipep)

            Try
                If baBuffer(0) <> ProtocolVersion Then
                    Return
                End If

                Dim baGUID As Byte() = New Byte(15) {}
                Array.Copy(baBuffer, 2, baGUID, 0, 16)

                Dim guid As New Guid(baGUID)

                Dim iCommPort As UInt16 = BitConverter.ToUInt16(baBuffer, 18)

                Dim bProtocolVersion As Byte = baBuffer(1)

                If guid <> zGUID Then ' ignore messages from myself
                    DataRoot.getDataRoot.createUserProfile(guid, ipep.Address, iCommPort, bProtocolVersion)
                End If
            Catch
                'silently ignore badly formatted messages
                Debug.WriteLine("EX: Badly formatted presence message")
            End Try
        End Sub

        Private Sub zTCPLA_AcceptCallback(ByVal ar As IAsyncResult)
            Try
                Dim tcpl As TcpListener = CType(ar.AsyncState, TcpListener)

                Dim tcp As TcpClient = tcpl.EndAcceptTcpClient(ar)

                ' start listening again
                tcpl.BeginAcceptTcpClient(New AsyncCallback(AddressOf zTCPLA_AcceptCallback), tcpl)

                DataRoot.getDataRoot.acceptConnection(CType(tcp.Client.RemoteEndPoint, IPEndPoint), tcp)
            Catch e As ObjectDisposedException
                ' do nothing, we're just shutting down
                Debug.WriteLine("EX: object disposed, we're shutting down")
            End Try
        End Sub
#End Region

#Region " IDisposable Support "
        Private disposedValue As Boolean = False        ' To detect redundant calls

        ' IDisposable
        Protected Overridable Sub Dispose(ByVal disposing As Boolean)
            If Not Me.disposedValue Then
                If disposing Then
                    ' free managed resources when explicitly called
                    zTmr = Nothing
                    zUDP = Nothing

                    Dim tcpl As TcpListener
                    For Each tcpl In zTCPLA
                        tcpl.Stop()
                        zTCPLA.Remove(tcpl)
                    Next
                End If

                ' free shared unmanaged resources
            End If
            Me.disposedValue = True
        End Sub

        ' This code added by Visual Basic to correctly implement the disposable pattern.
        Public Sub Dispose() Implements IDisposable.Dispose
            ' Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.
            Dispose(True)
            GC.SuppressFinalize(Me)
        End Sub
#End Region
    End Class
End Namespace
